package net.villonanny.entity;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.villonanny.Console;
import net.villonanny.ConversationException;
import net.villonanny.EventLog;
import net.villonanny.FatalException;
import net.villonanny.Synchronizer;
import net.villonanny.TimeWhenRunnable;
import net.villonanny.TranslationException;
import net.villonanny.Translator;
import net.villonanny.Util;
import net.villonanny.entity.RallyPoint.SendTroopsInfo;
import net.villonanny.strategy.Strategy;
import net.villonanny.strategy.WaveSender;
import net.villonanny.type.BuildingType;
import net.villonanny.type.BuildingTypeToBuildingMap;
import net.villonanny.type.ResourceType;
import net.villonanny.type.ResourceTypeMap;
import net.villonanny.type.ResourceTypeToFieldMap;
import net.villonanny.type.TribeType;
import net.villonanny.type.TroopSpeed;
import net.villonanny.type.TroopTransferType;
import net.villonanny.type.TroopType;
import net.villonanny.type.TroopTypeMap;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.configuration.SubnodeConfiguration;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.lang.ArrayUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;

public class Village {
	private final static Logger log = Logger.getLogger(Village.class);
	private static final String P_WHOLETAG = "<[^>]*>"; // Match a whole html tag, from < to >
	private static final String P_CLOSETAG = "[^>]*>"; // Match up to the closing element of a tag, i.e. to >
	private static final String P_NONDIGITS = "\\D*";
	private static final String P_NOQUOTES = "[^\"]*?";
	private static final String P_ANYWHITESPACE = "\\s*";
	private SubnodeConfiguration config;
	private String villageUrlString;
	private String villageSecondUrlString;
	private String villageName; // From travian
	private Util util;
	private ResourceTypeMap availableResources = new ResourceTypeMap();
	private ResourceTypeMap maxResources = new ResourceTypeMap();
	private ResourceTypeMap production = new ResourceTypeMap();
	// private int[] resourcesAtLastRun = new int[availableResources.length];
	private Map<String, Field> fields; // travianSlotId -> Field
	private Map<String, Building> buildings;
	private BuildingTypeToBuildingMap buildingMap;
//	private Map<String, EmptySite> emptySites;
	private Translator translator;
	private Map<String, Strategy> strategies;
	private List<Strategy> strategyList; // Ordered list
	private Date fieldQueueAvailableTime = null;
	private Date buildingQueueAvailableTime = null;
	private int constructionQueues;
	private boolean hasRallyPoint = true;
	private String RALLYPOINT_ID = "39";
//	private String RALLYPOINT_URL_SUFFIX = "build.php?id=" + RALLYPOINT_ID;
	private String WALL_ID = "40";
//	private String WALL_URL_SUFFIX = "build.php?id=" + WALL_ID;
	
	
	private Synchronizer sync;
	Calendar runtime;
	Server server;
	String timeToRun;

	/**
	 * Load village main page and fetch resources and next urls
	 * 
	 * @param config
	 * @param client
	 * @param serverName
	 * @param villageId
	 * @param navigator
	 * @param translator
	 * @throws ConversationException
	 */
	public Village(Util util, SubnodeConfiguration villageConfig,
			int constructionQueues) {
		this.villageUrlString = villageConfig.getString("/url");
		this.translator = util.getTranslator();
		this.constructionQueues = constructionQueues;
		// Create strategies
		this.strategies = new HashMap<String, Strategy>();
		this.strategyList = new ArrayList<Strategy>();
		updateConfig(villageConfig, util);
	}

	private Strategy createStrategy(SubnodeConfiguration strategyConfig) {
		Strategy result;
		String desc = "?";
		try {
			String className = strategyConfig.getString("/@class");
			desc = strategyConfig.getString("/@desc", className);
			String fullClassName = Strategy.class.getPackage().getName() + "." + className;
			result = (Strategy) Class.forName(fullClassName).newInstance();
		} catch (Exception e) {
			throw new FatalException("Invalid strategy \"" + desc + "\"", e);
		}
		return result;
	}

	public String gotoMainPage(boolean sharp) throws ConversationException {
		return util.httpGetPage(villageUrlString, sharp);
	}

	public String gotoMainPage() throws ConversationException {
		return gotoMainPage(false);
	}

	public String gotoSecondPage(boolean sharp) throws ConversationException {
		return util.httpGetPage(villageSecondUrlString, sharp);
	}

	public String gotoSecondPage() throws ConversationException {
		return gotoSecondPage(false);
	}

	/**
	 * Update village values from online page
	 * 
	 * @throws ConversationException
	 */
	public void update() throws ConversationException {
		Pattern p;
		Matcher m;
		// Fetch village page
		String page = gotoMainPage();

		// Fetch name
		// <h1>.A.Moncalieri</span></h1>
		p = Pattern.compile(Util.P_FLAGS + "<h1>([^<]*)<");
		m = p.matcher(page);
		if (m.find()) {
			villageName = m.group(1);
		} else {
			Util.saveTestPattern("Village name", p, page);
			throw new ConversationException("Can't find village name");
		}
		log.debug("Village is called " + villageName);

/*
		// Check if construction undergoing and fetch end time
		p = Pattern.compile(Util.P_FLAGS + "<img src=\".*?img/un/a/del.gif\".*?<td>(\\D+?) \\(.*?<span id=timer.*?>(\\d?\\d:\\d?\\d:\\d?\\d)</span> h.");
		m = p.matcher(page);
		if(!m.find()){
			p = Pattern.compile(Util.P_FLAGS + "<img[^>]*class=\"del\"[^>]*></a></td><td>(\\D+?) \\(.*?<span id=timer.*?>(\\d?\\d:\\d?\\d:\\d?\\d)</span> h.");
			m = p.matcher(page);
		}
		fieldQueueAvailableTime = null;
		buildingQueueAvailableTime = null;
		while (m.find()) {
			String type = m.group(1);
			String timeTaken = m.group(2);
			Date localCompletionTime = util.getCompletionTime(timeTaken);
			log.debug("localCompletionTime = " + localCompletionTime);
			if (localCompletionTime == null	|| localCompletionTime.before(new Date())) {
				log.debug("Invalid localCompletionTime");
				continue;
			}
			if (translator.isField(type)) {
				log.debug("Field on queue");
				fieldQueueAvailableTime = localCompletionTime;
			} else {
				log.debug("Building on queue");
				buildingQueueAvailableTime = localCompletionTime;
			}
		}
		if (fieldQueueAvailableTime == null) {
			fieldQueueAvailableTime = new Date(System.currentTimeMillis() - 1); // Queue available
			log.debug("Field queue available");
		}
		if (buildingQueueAvailableTime == null) {
			buildingQueueAvailableTime = new Date(System.currentTimeMillis() - 1); // Queue Available
			log.debug("Buildings queue available");
		}
		log.debug("Queues available at " + fieldQueueAvailableTime + " / " + buildingQueueAvailableTime);

		// Available resources
		for (ResourceType resourceType : ResourceType.values()) {
			String imagePath = resourceType.getImagePath();
			String ClassName = "r" + (resourceType.toInt()+1);

			p = Pattern.compile(Util.P_FLAGS + "<img[^>]*src=\".*?" + imagePath + P_CLOSETAG + "(?:</td>\\s*" + P_WHOLETAG + ")?\\D*?(\\d*?)/(\\d*?)</td>");
			m = p.matcher(page);
			if (!m.find()) {
				p = Pattern.compile(Util.P_FLAGS + "<td><img[^>]*class=\""+ClassName+P_CLOSETAG+"</td>\\s*<td"+P_CLOSETAG+"(\\d*?)/(\\d*?)</td>");
				m = p.matcher(page);
			}

			if (m.find()) {
				availableResources.put(resourceType, Integer.parseInt(m.group(1)));
				maxResources.put(resourceType, Integer.parseInt(m.group(2)));
			} else {
				Util.saveTestPattern("Available resources", p, page);
				throw new ConversationException("Can't find resources");
			}
			*/
/*
		}
		// Resource production
		for (ResourceType resourceType : ResourceType.values()) {
			String imagePath = resourceType.getImagePath();
			String ClassName = "r" + (resourceType.toInt()+1);
			*/
/*
			if (resourceType == ResourceType.FOOD) {
				continue;
			}
			// <td><img class="res" src="img/un/r/4.gif"></td><td>Cereais:</td><td align="right"><b>-8&nbsp;</b></td><td>por hora</td>
			p = Pattern.compile(Util.P_FLAGS + "<img[^>]*src=\".*?" + imagePath + P_CLOSETAG + P_NONDIGITS + "<b>(-?\\d+)");
			m = p.matcher(page);
			if (!m.find()) {
				p = Pattern.compile(Util.P_FLAGS + "<div id=\"production\">.*?<td><img[^>]*class=\""+ClassName+P_CLOSETAG+"</td>\\s*<td>[^<]*</td>\\s*<td align=\"right\"><b>(-?\\d+)&nbsp;</b></td>");
				m = p.matcher(page);
			}
			if (m.find()) {
				production.put(resourceType, Integer.parseInt(m.group(1)));
			} else {
				Util.saveTestPattern("Resource production", p, page);
				throw new ConversationException("Can't find resource production for \"" + resourceType + "\"");
			}
		}
		
		buildingMap = new BuildingTypeToBuildingMap();
		// Find fields
		this.fields = new HashMap<String, Field>();
		String levelPattern = translator.getMatchingPattern(Translator.LEVEL);
		// <area href="build.php?id=1" coords="101,33,28" shape="circle" title="Segheria livello 11">
		p = Pattern.compile(Util.P_FLAGS + "<area\\s*href=\"build\\.php\\?id=(\\d+)\"[^>]*?shape=\"circle\"\\s*title=\"(" + P_NOQUOTES + ") " + levelPattern + " (\\d+)\"/?>");
		m = p.matcher(page);
		int fieldsFound = 0;
		while (m.find()) {
			String itemIdString = m.group(1);
			String fieldTypeString = m.group(2);
			String fieldLevel = m.group(3);
			String name = fieldTypeString + " level " + fieldLevel;
			String urlEnd = "build.php?id=" + itemIdString;
			String url = Util.getFullUrl(villageUrlString, urlEnd);
			BuildingType fieldType = BuildingType.fromKey(translator.getKeyword(fieldTypeString));
			Field item = new Field(name, url, translator);
			item.setCurrentLevel(fieldLevel);
			item.setType(fieldType);
			this.fields.put(itemIdString, item);
			this.buildingMap.put(fieldType, item);
			fieldsFound++;
		}
		if (fieldsFound < 18) {
			Util.saveTestPattern("looking for fields (" + fieldsFound + ")", p, page);
			String msg = Util.getLocalMessage("msg.fieldsNotFound", this.getClass());
			throw new ServerFatalException(msg);
		}
*/

        buildingMap = new BuildingTypeToBuildingMap();
		// Find village center url
		p = Pattern.compile("(?s)area\\s*href=\"(dorf2.*?)\"\\s*coords=\".*?\"\\s*shape=\"circle\"");
		m = p.matcher(page);
		if (m.find()) {
			villageSecondUrlString = Util.getFullUrl(villageUrlString, m.group(1));
		} else {
			Util.saveTestPattern("village center url", p, page);
			throw new ConversationException("Can't find village center");
		}
		// Fetch village center page
		page = util.httpGetPage(villageSecondUrlString);
		
		// Find buildings
		this.buildings = new HashMap<String, Building>();
		// <area href="build.php?id=20" title="Magazzino livello 2" coords="136,66,136,12,211,12,211,66,174,87" shape="poly">
		// <area href="build.php?id=19" title="spazio edificabile"
		// coords="53,91,91,71,127,91,91,112" shape="poly">
		// I'm using (?:xxxxx)? to match empty sites as well
		//p = Pattern.compile(Util.P_FLAGS + "<area\\s*href=\"build\\.php\\?id=(\\d+)\"[^>]*?shape=\"circle\"\\s*title=\"(" + P_NOQUOTES + ") " + levelPattern + " (\\d+)\"/?>");
        String levelPattern = translator.getMatchingPattern(Translator.LEVEL);
		p = Pattern.compile(Util.P_FLAGS + "<area\\s*href=\"build\\.php\\?id=(\\d+)\"[^>]*?title=\"(" + P_NOQUOTES + ")(?: " + levelPattern + " (\\d+))?\"");
		m = p.matcher(page);
		int sitesFound = 0;
		hasRallyPoint = true;
		while (m.find()) {
			String itemIdString = m.group(1); // e.g. "34"
			String buildingTypeString = m.group(2); // e.g. "Caserma", "Spazio edificabile", "altro spazio edificabile"
			String buildingLevel = m.group(3); // e.g. "3" or null
			String name = buildingTypeString + (buildingLevel!=null ? " level " + buildingLevel : "");
			String urlEnd = "build.php?id=" + itemIdString;
			String url = Util.getFullUrl(villageUrlString, urlEnd);
			// Make instance
			try {
				String buildingKey = translator.getKeyword(buildingTypeString);
				if (buildingKey!=null) {
					BuildingType buildingType = BuildingType.fromKey(buildingKey);
					Building item = (Building) buildingType.getInstance(name, url, translator);
					item.setCurrentLevel(buildingLevel);
					item.setType(buildingType);
					this.buildings.put(itemIdString, item);
					this.buildingMap.put(buildingType, item);
				}
			} catch (TranslationException e) { // buildingTypeString not found (e.g. "altro spazio edificabile")
				if (itemIdString.equals(RALLYPOINT_ID)) {
					this.buildings.put(itemIdString, new RallyPointEmptySite(name, url, translator));
					hasRallyPoint = false; // Still empty
				} else if (itemIdString.equals(WALL_ID)) {
					if (this.buildings.containsKey(itemIdString)) {
						continue; // happens 3 times in total
					}
					this.buildings.put(itemIdString, new WallEmptySite(name, url, translator));
				} else {
					throw e;
				}
			}
			sitesFound++;
		}
		if (sitesFound < 22) {
			Util.saveTestPattern("looking for buildings", p, page);
			String msg = Util.getLocalMessage("msg.buildingsNotFound", this.getClass());
			throw new ServerFatalException(msg);
		}
	}

	public Util getUtil() {
		return util;
	}

	// public SubnodeConfiguration getConfig() {
	// return config;
	// }

	public String getDesc() {
		String s = villageName != null ? villageName : "?";
		return config.getString("/@desc", s);
	}

	public Collection<Field> getFields() {
		return fields.values();
	}

	public Collection<Building> getBuildings() {
		return buildings.values();
	}

//	public Collection<EmptySite> getEmptySites() {
//		return emptySites.values();
//	}
//
	public int getAvailableFood() {
		return maxResources.get(ResourceType.FOOD) - availableResources.get(ResourceType.FOOD);
	}

	public ResourceTypeMap getProduction() {
		return production;
	}

	public ResourceTypeMap getAvailableResources() {
		return availableResources;
	}

	public ResourceTypeMap getMaxResources() {
		return maxResources;
	}

	public Translator getTranslator() {
		return translator;
	}

	public VillageRunInfo execute() throws ConversationException {
		NDC.push("(" + config.getString("/@desc", "?") + ")");
		boolean needsUpdate = true;
		try {
			// Run through each strategy
			int strategyIdx = 1;
//			List<StrategyRunner> strategyRunners = new ArrayList<StrategyRunner>();
			List<PostMethod> methods = new ArrayList<PostMethod>();
			List<Integer> strategyTimes = new ArrayList<Integer>();
			for (Strategy strategy : this.strategyList) {
				if (strategy.isDeleted() || Console.getInstance().isQuitting()) {
					// Might have been deleted by a config reload in the meantime
					continue;
				}
				
				try {
					Console.getInstance().checkFlags();
					TimeWhenRunnable timeWhenRunnable = strategy.getTimeWhenRunnable();
					if (timeWhenRunnable == null
							|| timeWhenRunnable.before(new Date())
							|| (timeWhenRunnable!=TimeWhenRunnable.NEVER && resourcesAvailable(strategy.getTriggeringResources()))) {
						if (log.isDebugEnabled()) {
							if (timeWhenRunnable != null
									&& !timeWhenRunnable.before(new Date())
									&& resourcesAvailable(strategy.getTriggeringResources())) {
								EventLog.log("Strategy triggered by resources");
							}
						}
						EventLog.log("Executing strategy \"" + strategy.getDesc() + "\"");
						if (needsUpdate) {
							update(); // Refresh village properties and position to this village
						}
						PostMethod postMethod = null;
						Integer secToArrive = null;
						if (strategy instanceof WaveSender) {
							SendTroopsInfo sendTroopsInfo = ((WaveSender)strategy).executeEx();
							postMethod = sendTroopsInfo.postMethod;
							secToArrive = sendTroopsInfo.secondsToArrive;
							
						}
						methods.add(postMethod);
						strategyTimes.add(secToArrive);
                        log.info("secToArrive=" + secToArrive);
						strategyIdx++;
//						strategyRunners.add( new StrategyRunner(strategy, strategyIdx) );
						
//						TimeWhenRunnable nextTime = strategy.execute();
//						
//						
//						if (nextTime == null) { // Just to be safe
//							log.warn("(Internal Error) Shouldn't return null");
//							nextTime = new TimeWhenRunnable(System.currentTimeMillis());
//						}
//						log.debug(String.format("Strategy %s will be run after %s ", strategy.getDesc(), nextTime.getTimeWhenRunnable()));
//						strategy.setTimeWhenRunnable(nextTime);
//						needsUpdate = strategy.modifiesResources();
					}
				} catch (ConversationException e) {
					String s = "Error while executing strategy \"" + strategy.getDesc() + "\" (skipping)";
					Util.log(s, e);
					EventLog.log(s);
					Util.shortestPause(false); // Just to be safe
					//throw e;
					// Just keep going to the next strategy
				} catch (SkipRequested e) {
					// Skip strategy: just keep going
					log.debug("Skipped");
				} catch (SkipVillageRequested e) {
					// Propagate
					log.debug("Village skipped");
					throw e;
				} catch (TranslationException e) {
					EventLog.log(e.getMessage());
					log.error("Translation error", e);
					// Keep going
				} catch (Exception e) {
					// Any other exception skips the strategy but keeps the village going
					// so that bugs in one strategy don't prevent other strategies from executing
					String message = EventLog.log("msg.strategyException", this.getClass(), strategy.getDesc() + "(" + strategy.getClass().getSimpleName() + ")" );
					log.error(message, e);
				}
			}
			
			// check all strategy speeds
			for (int i = 1; i < strategyTimes.size(); i++) {
				if ( ! strategyTimes.get(i).equals(strategyTimes.get(0)) ) {
					throw new RuntimeException("Invalid config, all strategies inside village " + getDesc() + " must have same speed.");
				}
			}
			
			return new VillageRunInfo(strategyTimes.get(0), methods);
			
			// create synchronizer
//			System.out.println("Creating sync...");
//			sync = new Synchronizer(strategyRunners.size(), runtime);
			
//			int threadId = 0;
//			for (StrategyRunner strategyRunner : strategyRunners) {
////				System.out.println("Running thread " + threadId++);
//				Thread t = new Thread(strategyRunner);
//				t.setName( String.valueOf(threadId) );
//				t.start();
//				threadId++;
//			}
//			System.out.println("Running threads finished.");
			// // Update available resources
			// update();
			// // Record available resources
			// System.arraycopy(availableResources, 0, resourcesAtLastRun, 0,
			// availableResources.length);
		} finally {
			NDC.pop();
		}
		
//		System.out.println("Village finished");
		
	}

	public boolean resourcesAvailable(ResourceTypeMap resMap) {
		if (resMap == null) {
			return false;
		}
		for (ResourceType resource : availableResources.keySet()) {
			if (availableResources.get(resource) < resMap.get(resource)) {
				return false;
			}
		}
		return true;
	}

	public TimeWhenRunnable getTimeWhenRunnable() throws ConversationException {
		// Find the earliest strategy date
		TimeWhenRunnable result = TimeWhenRunnable.NEVER; // Runnable false
		boolean updateDone = false;
		for (Strategy strategy : this.strategies.values()) {
			TimeWhenRunnable timeWhenRunnable = strategy.getTimeWhenRunnable();
			ResourceTypeMap triggeringRes = strategy.getTriggeringResources();
			if (timeWhenRunnable != null && timeWhenRunnable != TimeWhenRunnable.NEVER && timeWhenRunnable.after(new Date())
					&& triggeringRes != null && !updateDone) {
				// Update resources if they might trigger
				update();
				updateDone = true;
			}
			if (timeWhenRunnable == null || (timeWhenRunnable != TimeWhenRunnable.NEVER && resourcesAvailable(triggeringRes))) {
				return new TimeWhenRunnable(System.currentTimeMillis() - 1); // run now
			} else if (timeWhenRunnable.before(result)) {
				result = timeWhenRunnable;
			}
		}
		return result;
	}

	public boolean fieldQueueAvailable() {
		Date now = new Date();
		if (constructionQueues == 1) {
			return this.fieldQueueAvailableTime.before(now)
					&& this.buildingQueueAvailableTime.before(now);
		} else {
			return this.fieldQueueAvailableTime.before(now); // TODO there might be more than 1, and 1 may be available
		}
	}

	public boolean buildingQueueAvailable() {
		Date now = new Date();
		if (constructionQueues == 1) {
			return this.fieldQueueAvailableTime.before(now)
					&& this.buildingQueueAvailableTime.before(now);
		} else {
			return this.buildingQueueAvailableTime.before(now); // TODO there might be more than 1, and 1 may be available
		}
	}

	public Date getBuildingQueueAvailableTime() {
		if (constructionQueues == 1) {
			return fieldQueueAvailableTime.after(buildingQueueAvailableTime) ? fieldQueueAvailableTime
					: buildingQueueAvailableTime;
		}
		return buildingQueueAvailableTime;
	}

	public Date getFieldQueueAvailableTime() {
		if (constructionQueues == 1) {
			return fieldQueueAvailableTime.after(buildingQueueAvailableTime) ? fieldQueueAvailableTime
					: buildingQueueAvailableTime;
		}
		return fieldQueueAvailableTime;
	}

	public Date getQueueAvailableTime(UpgradeableSite item) {
		if (constructionQueues == 1) {
			return fieldQueueAvailableTime.after(buildingQueueAvailableTime) ? fieldQueueAvailableTime : buildingQueueAvailableTime;
		}
		if (item instanceof Field) {
			return fieldQueueAvailableTime;
		}
		return buildingQueueAvailableTime;
	}
	
//	public Date getQueueAvailableTime(ConstructionData data) {
//		if (constructionQueues == 1) {
//			return fieldQueueAvailableTime.after(buildingQueueAvailableTime) ? fieldQueueAvailableTime
//					: buildingQueueAvailableTime;
//		}
//		// TODO: Compare the building ID or the GID to know if its a field or a building
//		return buildingQueueAvailableTime;
//	}

	public void setQueueAvailableTime(UpgradeableSite item, Date availableTime) {
		if (constructionQueues == 1) {
			fieldQueueAvailableTime = availableTime;
			buildingQueueAvailableTime = availableTime;
			return;
		}
		if (item instanceof Field) {
			fieldQueueAvailableTime = availableTime;
		} else {
			buildingQueueAvailableTime = availableTime;
		}
	}

	public boolean constructionQueueAvailable(UpgradeableSite item) {
		Date now = new Date();
		return getQueueAvailableTime(item).before(now);
	}

	public RallyPoint getRallyPoint() {
		return (RallyPoint) (hasRallyPoint ? buildings.get(RALLYPOINT_ID) : null);
	}

	public String getVillageName() {
		return villageName;
	}

	public String getVillageUrlString() {
		return villageUrlString;
	}

	public UpgradeableSite getItem(String itemId) {
		UpgradeableSite item = fields.get(itemId);
		return item != null ? item : buildings.get(itemId);
	}

	public void terminate() {
		EventLog.log("evt.villageTerminated", this.getClass(), getDesc());
	}

	public void updateConfig(SubnodeConfiguration villageConfig, Util util) {
//		EventLog.log("Updating village config for " + getVillageName());
		// Need to pass util as well, because it has the new serverConfig inside
		this.config = villageConfig;
		this.util = util;
		List<Strategy> deletableStrategies = new ArrayList<Strategy>(strategies.values());
		List<Strategy> addedStrategies = new ArrayList<Strategy>();
		this.strategyList = new ArrayList<Strategy>();
		List<SubnodeConfiguration> strategyConfigs = config.configurationsAt("/strategy[@enabled='true']");
		int strategyIdx = 1;
		for (SubnodeConfiguration strategyConfig : strategyConfigs) {
//			Console.getInstance().checkFlags(); // This is wrong
			// Create a candidate strategy
			Strategy candidate = createStrategy(strategyConfig);
			// Check if it is already listed
			String idFromConfig = candidate.createId(strategyConfig);
			Strategy oldStrategy = this.strategies.get(idFromConfig);
			
			/*
			if (oldStrategy != null) {
				if (addedStrategies.contains(candidate)) {
					// Strategy just added
					EventLog.log("evt.duplicateStrategy", this.getClass(), candidate.getDesc());
					continue;
				}
				// Reconfigure existing
				oldStrategy.updateConfig(strategyConfig, util);
				deletableStrategies.remove(oldStrategy);
				strategyList.add(oldStrategy);
				System.out.println("Setting index(*) [" +  strategyIdx +"] for " + oldStrategy);
				oldStrategy.setIndex(strategyIdx++);
				
			} else {
				// Add new
				addedStrategies.add(candidate);
				candidate.init(strategyConfig, this);
				this.strategies.put(candidate.getId(), candidate);
				strategyList.add(candidate);
				System.out.println("Setting index(**) [" +  strategyIdx +"] for " + candidate);
				candidate.setIndex(strategyIdx++);
			}
			*/
			
			// Add new
			addedStrategies.add(candidate);
			candidate.init(strategyConfig, this);
			this.strategies.put(candidate.getId(), candidate);
			strategyList.add(candidate);
			candidate.setIndex(strategyIdx++);
			
		}
		// Delete disabled or removed strategies
		for (Strategy strategy : deletableStrategies) {
			strategy.setDeleted(true);
			this.strategies.remove(strategy.getId());
			EventLog.log("evt.strategyRemoved", this.getClass(), strategy.getId());
		}
	}

	public Collection<Strategy> getStrategies() {
		return strategies.values();
	}

	public ResourceTypeToFieldMap getLowestFieldPerType() {
		Collection<Field> fields = getFields();
		// For each type, find field at lowest level
		ResourceTypeToFieldMap candidatesPerType = new ResourceTypeToFieldMap();
		for (Field item : fields) {
			ResourceType resourceType = item.getResourceType();
			Field candidate = candidatesPerType.get(resourceType);
			if (candidate==null || item.getCurrentLevel() < candidate.getCurrentLevel()) {
				candidatesPerType.put(resourceType, item);
			}
		}
		return candidatesPerType;
	}

	public MarketSite getMarket() {
		return (MarketSite) buildingMap.getOne(BuildingType.MARKETPLACE);
	}

	/**
	 * The BuildingTypeToBuildingMap can be used to retrieve any available building/field in the village.
	 * @return
	 */
	public BuildingTypeToBuildingMap getBuildingMap() {
		return buildingMap;
	}
	
	public Synchronizer getSync() {
		return sync;
	}
	/*
	int secondsToGo() {
		
		// village coordinates
//		int vx, vy;
//		vx = config.getInt("/@x");
//		vy = config.getInt("/@y");
//		
//		System.out.println("Village coordinates: (" + vx+"|" + vy + ")");
		
//		TribeType tribeType = server.getTribeType();
		
		List<SubnodeConfiguration> strategyConfigs = config.configurationsAt("/strategy[@enabled='true']");
//		List<Integer> strategySpeeds = new ArrayList<Integer>();
		
		List<Integer> strategyTimes = new ArrayList<Integer>();
		
		for (SubnodeConfiguration strategyConfig : strategyConfigs) {
			List<SubnodeConfiguration> troopsNodes = strategyConfig.configurationsAt("/troops");
			
			EventLog.log("Strategy: " + strategyConfig.getString("/@desc"));
			
			String x = strategyConfig.getString("/target/@x");
			String y = strategyConfig.getString("/target/@y");
			String village = strategyConfig.getString("/target/@village", "");
			EventLog.log("Target coordinates: (" + x+"|" + y + ")");
			String movement = strategyConfig.getString("/target/@movement", "attack");
			String attKey = "movement." + translator.getKeyword(movement, "movement"); // movement.attack
			TroopTransferType transferType = TroopTransferType.fromKey(attKey);
//			
			TroopTypeMap toSend = new TroopTypeMap();
			for (SubnodeConfiguration troopsNode : troopsNodes) {
//				boolean enabled = troopsNode.getBoolean("/@enabled", true); // Enabled by default
//				if (!enabled) {
//					continue;
//				}
				String type = troopsNode.getString("/@type");
				String fullkey = util.getTranslator().getKeyword(type); // romans.troop1
				String typeKey = fullkey.substring(fullkey.indexOf(".")+1);
				TroopType troopType = TroopType.fromString(typeKey);
				int val = troopsNode.getInt("/");
				toSend.put(troopType, val);
				
				
				
				
			}
			
			try {
				int sec = getRallyPoint().sendTroops(util, x, y, village, toSend, transferType,  new String[] {null,null}, true, true, null).secondsToArrive;
				EventLog.log("Seconds to go: " + sec);
				strategyTimes.add(sec);
			} catch (ConversationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			
		}
		
		// check all strategy speeds
		for (int i = 1; i < strategyTimes.size(); i++) {
			if ( ! strategyTimes.get(i).equals(strategyTimes.get(0)) ) {
				throw new RuntimeException("Invalid config, all strategies inside village must have same speed.");
			}
		}
		
		return strategyTimes.get(0);
		
//		double distance = TroopSpeed.karten_distanz(vx, vy, x, y);
//		
//		return TroopSpeed.karten_seconds(strategyTimes.get(0), distance);
	}
	*/
	class StrategyRunner implements Runnable {

		private Strategy strategy;
		private int index;
		
		StrategyRunner(Strategy s, int index) {
			this.strategy = s;
			this.index = index;
		}
		
		public void run() {
			log.info("Running strategy " + index);
			try {
				this.strategy.execute();
			} catch (ConversationException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	class VillageRunInfo {
		private int secondsToGo;
		private List<PostMethod> methods;
		
		private VillageRunInfo(int secondsToGo, List<PostMethod> methods) {
			super();
			this.secondsToGo = secondsToGo;
			this.methods = methods;
		}
		public int getSecondsToGo() {
			return secondsToGo;
		}
		public List<PostMethod> getMethods() {
			return methods;
		}
	}

	
}

